package com.smoed.common.logging;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

import com.smoed.common.utilities.StringUtils;

public final class Log {
    private Log() {
    }

    private enum LogType {
        TEST(1), INFO(2), WARN(3), ERROR(4), FATAL(5);

        private final int debugLevel;

        private LogType(int debugLevel) {
            this.debugLevel = debugLevel;
        }

        public int getDebugLevel() {
            return debugLevel;
        }
    }

    // TODO read these settings in from config file
    private static final int DEBUG_LEVEL = 1;
    private static final String LOG_NAME = "main.log";
    private static final int MAX_LOG_SIZE_IN_BYTES = 1048576; // 1MB
    private static StringBuilder logCache;
    private static int currentLogSize = 0;

    private static FileWriter out;
    private static final int INITIAL_STRINGBUILDER_SIZE = 100;
    private static final int DEFAULT_STACK_LEVEL = 3;

    private static final Date currentDate;
    private static final SimpleDateFormat formatter;
    private static final String COLON = ": ";
    private static final String AT = " @ ";

    static {
        if (DEBUG_LEVEL > 0) {
            currentDate = new Date();
            formatter = new SimpleDateFormat("EEE h:mm:ss.SSS a");
            initLog();
        }
    }

    private static void initLog() {
        try {
            out = new FileWriter(new File(LOG_NAME), false);
            if (logCache != null)
                out.write(logCache.toString());
            logCache = new StringBuilder((int) (MAX_LOG_SIZE_IN_BYTES*.1));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    private static void println(LogType type, String str, int stackLevel) {
        if (type.getDebugLevel() >= DEBUG_LEVEL) {

            currentDate.setTime(System.currentTimeMillis());
            final String timestamp  = formatter.format(currentDate);

            final StringBuilder logMessageBuilder = new StringBuilder(INITIAL_STRINGBUILDER_SIZE);

            logMessageBuilder.append(type).append(COLON)
                .append(str).append(AT).append(timestamp)
                .append(' ');

            if (stackLevel < 0)
                logMessageBuilder.append(Thread.currentThread().getStackTrace());
            else {
                final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
                if (stackLevel >= stackTrace.length)
                    stackLevel = stackTrace.length-1;

                logMessageBuilder.append(stackTrace[stackLevel]);
            }

            final String logMessage = logMessageBuilder.append(StringUtils.NEWLINE).toString();

            synchronized (out) {
                // Clear out log every MAX_LOG_SIZE_IN_BYTES bytes
                if (currentLogSize > MAX_LOG_SIZE_IN_BYTES)
                    initLog();

                try {
                    out.write(logMessage);
                    out.flush();
                    currentLogSize += logMessage.length();
                } catch (IOException e) {
                    e.printStackTrace();
                }


                logCache.append(logMessage);
            }
            System.out.print(logMessage);
        }
    }

    public static void info(String str) {
        println(LogType.INFO, str, DEFAULT_STACK_LEVEL);
    }

    public static void info(String str, int stackLevel) {
        println(LogType.ERROR, str, stackLevel);
    }

    public static void warn(String str) {
        println(LogType.WARN, str, DEFAULT_STACK_LEVEL);
    }

    public static void warn(String str, int stackLevel) {
        println(LogType.WARN, str, stackLevel);
    }

    public static void test(String str) {
        println(LogType.TEST, str, DEFAULT_STACK_LEVEL);
    }

    public static void test(String str, int stackLevel) {
        println(LogType.TEST, str, stackLevel);
    }

    public static void error(String str) {
        println(LogType.ERROR, str, DEFAULT_STACK_LEVEL);
    }

    public static void error(String str, int stackLevel) {
        println(LogType.ERROR, str, stackLevel);
    }

    public static void fatal(String str) {
        println(LogType.FATAL, str, DEFAULT_STACK_LEVEL);
    }

    public static void fatal(String str, int stackLevel) {
        println(LogType.FATAL, str, stackLevel);
    }
}
